#ifndef BSP_H
#define BSP_H

#include <stdio.h>
#include <vector>
#include <set>
#include <algorithm>

#include <iostream>
//#include <Windows.h>
#include "Matrices_old.h"
//#include "globals.h"
#include "PolyPlane.h"
#include "RayTracing.h"

using namespace std;

//#define epsilon 0.001f
const int MAX_POLY_POINTS = 128;
const int MAX_BRUSH_POINTS = 1024;
const int MAX_BRUSH_POLYS = 1024;

const float MINIMUM_RELATION = 0.8f;
const float MIN_RELATION_SCALE = 2.0f;

const int NO_NEED_TO_DIVIDE = -3;
/*
typedef struct Plane{
	float n[3], d;

	bool operator==(Plane rhs)
	{
		if(rhs.d == d)
		{
			if(rhs.n[0] == n[0])
				if(rhs.n[1] == n[1])
					if(rhs.n[2] == n[2])
						return true;
		}
		return false;
	}
}Plane;

typedef struct Poly{
	//float* points;
	//int num_points;
	vector<float> points;
	bool legal;
	Poly() : legal(true){}

	bool operator==(Poly rhs)
	{
		if(rhs.points.size() == points.size())
		{
			if(equal(points.begin(), points.end(), rhs.points.begin()))
			{
				return true;
			} else
			{
				return false;
			}
		} else
		{
			return false;
		}
	}
}Poly;

enum {FRONT, BACK, ONPLANE, SPANNING, TOUCH_FRONT, TOUCH_BACK};
*/


template<class T>
inline T Round(T arg) {
	int r_arg = static_cast<int>(arg);
	T diff = arg - static_cast<T>(r_arg);
	if(diff > static_cast<T>(0.55))
	{
		return (r_arg + static_cast<T>(1));
	} else
	{
		if(diff < static_cast<T>(-0.55))
		{
			return (r_arg - static_cast<T>(1));
		} else
		{
			return static_cast<T>(r_arg);
		}
	}
}

class Brush{

	float bbox_min_[3];
	float bbox_max_[3];
	float center_[3];
	
	vector<float> sides_;
	//int num_sides_;

	//Plane* planes_;
	vector<Plane> planes_;
	//int num_planes_;

	//Poly* polygons_;
	vector<Poly> polygons_;

	bool selected_;

	bool planes_update_needed_;
	vector<float*> p_selected_points_;

	bool freeze_texture_;

public:
	Brush() : selected_(false), freeze_texture_(false) {};
	Brush(vector<float>& sides);
	Brush(const Brush& brush_to_copy);
	~Brush();

	bool operator==(const Brush rhs) const
	{
		if(polygons_ == rhs.polygons_ && planes_ == rhs.planes_)
		{
			return true;
		}
		return false;
	}

	vector<Plane>& planes() {
		return planes_;
	}
	
	vector<Poly>& polygons() {
		return polygons_;
	}

	int num_planes() {
		return planes_.size();
	}

	const float* bbox_min(){ return bbox_min_; }
	const float* bbox_max(){ return bbox_max_; }

	void set_freeze_texture(bool b){ freeze_texture_ = b; }
	void set_selected(bool b){ selected_ = b; }

	void InitializeMesh();
	void UpdateUVs();
	void ClipToBrush(Brush& brush, bool clip_on_plane);
	void Draw(int col);
	void DrawWTexture();
	void DrawVertices();

	void Move(const float vec[3]);
	void Rotate(const float axis[3], const float angle);
	void Rotate(const float axis[3], const float angle, const float center[3]);
	void Rescale(const float old_box_min[3], const float old_box_max[3], const float new_box_min[3], const float new_box_max[3]);
	bool TraceL(const float orig[3], const float dir[3], float* dist);
	void UpdateBbox();
	bool SelectPoints(float x, float y, const float* view_proj_mat, const float* dims, const int* dims_px);
	bool SelectFace(const float orig[3], const float dir[3], float* dist, Poly** face);
	void MoveSelected(const float vec[3], int grid_size);
	void RebuildFromPolys();
	void ScaleTexture(float ds);
	void SetTexture(std::shared_ptr<GLTexture> tex);
	void ResetTexture();
	void GetUsedTextures(std::set<std::shared_ptr<GLTexture>>& list);
	//void ReplaceTexture(const unsigned int old_id, const unsigned int new_id);
	void SetTexVec(std::vector<float>::iterator& vec, std::vector<std::string>& tex_names);
};

class Portal;
class GLStaticMesh;

class BSPNode{

	Plane div_plane_;
	//vector<Poly> portals_;
	vector<Portal> portals_;

	vector<Portal*> leaf_portals_;
	//vector<Portal*> portals_in_;
	//vector<Portal*> portals_out_;

	BSPNode* front_node_;
	BSPNode* back_node_;
	BSPNode* parent_node_;

	vector<Poly> polygons_;
	vector<Plane> planes_;

	vector<Poly> originals_;

	float bbox_min_[3];
	float bbox_max_[3];
	float bbox_points_[8][3];
	float center_[3];

	vector<BSPNode*> pvs_;

	bool has_polys_;

	int id_;

	bool drawn_;

	vector<GLStaticMesh*> p_static_meshes_;

public:

	BSPNode(BSPNode* front, BSPNode* back, Plane divider);
	BSPNode(vector<Poly>& polygons, vector<Plane>& planes);
	~BSPNode();

	Plane& div_plane(){return div_plane_;}
	BSPNode* front_node(){return front_node_;}
	BSPNode* back_node(){return back_node_;}
	BSPNode* parent_node(){return parent_node_;}

	//vector<Plane> planes(){return planes_;}
	vector<Poly> originals(){return originals_;}

	unsigned int num_vtx_indices(){
		unsigned int ret_val = 0;
		for(auto& poly : polygons_)
		{
			ret_val += poly.vtx_indices.size();
		}
		return ret_val;
	}

	vector<Poly> const &polygons() const {
		return polygons_;
	}

	vector<Plane> const &planes() const {
		return planes_;
	}

	vector<BSPNode*>& pvs(){return pvs_;}

	void AddToPvs(BSPNode* node){
		auto res = std::find(pvs_.begin(), pvs_.end(), node);
		if(res == pvs_.end() || pvs_.empty())
		{
			pvs_.push_back(node);
		}
	}

	int id(){return id_;}

	bool has_polys(){return has_polys_;}

	vector<Portal*>& leaf_portals(){return leaf_portals_;}

	vector<GLStaticMesh*>& p_static_meshes(){return p_static_meshes_;}

	float* center(){return center_;}
	float* bbox_min(){return bbox_min_;}
	float* bbox_max(){return bbox_max_;}

	bool drawn(){return drawn_;}

	void set_parent_node(BSPNode* parent){parent_node_ = parent;}
	void set_front_node(BSPNode* front){front_node_ = front;}
	void set_back_node(BSPNode* back){back_node_ = back;}

	void set_originals(vector<Poly>& orig){originals_.swap(orig);}

	void set_drawn(bool drawn){drawn_ = drawn;}

	//void AddPortalIn(Portal* portal){portals_in_.push_back(portal);}
	//void AddPortalOut(Portal* portal){portals_out_.push_back(portal);}

	void AddPortal(Portal* portal){leaf_portals_.push_back(portal);}
	int Traverse(const float pos[3]);
	void Draw();
	void DrawPortals();
	void GeneratePortals();
	void BuildPVS();
	bool HasNonLegal();
	void UpdateUVs();
	void Finish(vector<BSPNode*>& nodes_array);
	void JoinVisibleFaces();
	void UpdateBbox();
	void ReorderNodes(vector<BSPNode*>& nodes_array);
	bool IsInPvs(BSPNode* node);

	vector<Portal> ClipPortals(vector<Portal>& portals);

	vector<Portal> ClipPortals_LessRecursion(Portal& portal, vector<Portal>& do_again_portals);

	void GetUsedTextures(std::set<std::shared_ptr<GLTexture>>& list);

	void AddMesh(GLStaticMesh* mesh);
	void ListMeshes(std::set<GLStaticMesh*>& list);

	static int num_nodes;
	static int num_drawn;

	static vector<float> vertices_pos_list;
	static vector<float> vertices_uv_list;
	static vector<float> vertices_normal_list;

};

class Portal {

	Poly polygon_;
	Plane plane_;
	BSPNode* front_node_;
	BSPNode* back_node_;

public: 
	Portal() : front_node_(nullptr), back_node_(nullptr){};
	Portal(Poly polygon, Plane plane);
	Portal(Poly polygon, Plane plane, BSPNode* front, BSPNode* back);
	//Portal(const Portal& portal);
	~Portal();

	Plane& plane(){return plane_;}
	Poly& polygon(){return polygon_;}
	BSPNode* front_node(){return front_node_;}
	BSPNode* back_node(){return back_node_;}

	void set_front_node(BSPNode* front){front_node_ = front;}
	void set_back_node(BSPNode* back){back_node_ = back;}

	void Draw();

};

bool GetIntersection(Plane& pl1, Plane& pl2, Plane& pl3, float* p);
/*
int ClassifyPoint (Plane& plane, float point[3]);
int ClassifyPolygon (Plane& plane, Poly& poly);
int ClassifyPolygon2 (Plane& plane, Poly& poly);
*/
vector<Poly> ClipPolyToList (Plane& poly_plane, Poly& poly, vector<Plane>& clippers, unsigned int clipper_index, bool clip_on_plane);

vector<Poly> ClipPolyToList2 (Plane& poly_plane, Poly& poly, vector<Plane>& clippers, vector<Poly>& clipper_polys, unsigned int clipper_index, bool clip_on_plane);

void SplitPoly(Poly& poly, Plane& plane, Poly& front, Poly& back);

Brush* CSGUnion(Brush* brushes, int num_brushes);


extern BSPNode* root_node;
extern vector<BSPNode*> nodes_array;


void NewFunc(Brush* brushes, int num_brushes);

int ChoseDividingPolygon(vector<Poly>& poly_list, vector<Plane>& plane_list, vector<Poly>& prev_dividers);

void BuildTree(BSPNode*& node, vector<Poly>& polygons, vector<Plane>& planes, vector<Poly>& original_polys);

void DrawOutside();

void DeleteConnectedNodes(vector<BSPNode*>& nodes, int node_index, vector<Portal*>& portals);

vector<BSPNode*> RecursePVS(BSPNode* source_leaf, Portal* source_portal, BSPNode* target_leaf, Portal* target_portal, int depth);
void RecursePVS2(BSPNode* source_leaf, Portal* source_portal, BSPNode* target_leaf, Portal* target_portal, int depth, std::vector<BSPNode*>& out_nodes, std::vector<Portal*>& portal_chain, std::vector<BSPNode*>& nodes_chain);

void ConstructClipPlanes(Portal* source_portal, Portal* target_portal, std::vector<Plane>& out_planes);

bool ClipToAntiPenumbra(Portal* source_portal, std::vector<Plane>& clip_planes, Portal& clipped_gen_portal);

bool ClipToAntiPenumbra(Portal* source_portal, Portal* target_portal, Portal& clipped_gen_portal);

extern float color_table[1024][3];

#endif
